package com.macro.mall.gateway.filter;

import com.macro.mall.common.util.MDCUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author tiger
 */
@Component
@RequiredArgsConstructor
public class TraceIdFilter implements GlobalFilter, Ordered {

    private final Tracer tracer;
    private static final AtomicInteger sequence = new AtomicInteger(1000);
    private static final int SEQUENCE_MAX = 9000;
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyMMddHHmmssSSS");

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 从请求头中获取 traceId
        String traceId = exchange.getRequest().getHeaders().getFirst("traceId");
        
        // 如果请求头中没有 traceId，则生成一个新的
        if (traceId == null) {
            traceId = generateTraceId();
        }
        // 将 traceId 放入 MDC 中，用于日志记录
        MDCUtil.setTraceId(traceId);

        // 获取当前Sleuth自动创建的Span
        Span currentSpan = tracer.currentSpan();
        String sTraceId = null;
        if (currentSpan != null) {
            sTraceId = currentSpan.context().traceId();
            MDCUtil.setSTraceId(sTraceId);
        }

        try {
            // 设置响应头中的 traceId
            exchange.getResponse().getHeaders().add("traceId", traceId);
            
            // 设置请求头中的 traceId，确保后续服务能获取到
            ServerHttpRequest.Builder requestBuilder = exchange.getRequest().mutate()
                    .header("traceId", traceId);
            
            // 如果有Sleuth生成的traceId，也一并传递
            if (sTraceId != null) {
                requestBuilder.header("sTraceId", sTraceId);
            }
            
            ServerHttpRequest modifiedRequest = requestBuilder.build();
            
            ServerWebExchange modifiedExchange = exchange.mutate()
                    .request(modifiedRequest)
                    .build();
            
            // 继续处理过滤器链
            return chain.filter(modifiedExchange);
        } finally {
            // 清理MDC上下文
            MDCUtil.clear();
        }
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }

    public static String generateTraceId() {
        StringBuilder traceId = new StringBuilder();

        // 1. 获取服务器IP的十六进制表示
        String hexIp = getHexIp();
        traceId.append(hexIp);

        // 2. 获取当前时间
        String time = dateFormat.format(new Date());
        traceId.append(time);

        // 3. 获取自增序列
        int currentSeq = sequence.getAndIncrement();
        if (currentSeq >= SEQUENCE_MAX) {
            sequence.set(1000);
            currentSeq = sequence.getAndIncrement();
        }
        traceId.append(String.format("%04d", currentSeq));

        // 4. 获取当前进程ID
        String processId = getProcessId();
        traceId.append(processId);

        return traceId.toString();
    }

    private static String getHexIp() {
        try {
            InetAddress ip = InetAddress.getLocalHost();
            byte[] bytes = ip.getAddress();
            StringBuilder hexIp = new StringBuilder();
            for (byte b : bytes) {
                hexIp.append(String.format("%02x", b));
            }
            return hexIp.toString();
        } catch (UnknownHostException e) {
            throw new RuntimeException("无法获取本地IP地址", e);
        }
    }

    private static String getProcessId() {
        // processName通常包含进程ID和主机名，格式为"进程ID@主机名"
        String processName = java.lang.management.ManagementFactory.getRuntimeMXBean().getName();
        return processName.split("@")[0];
    }
}